using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Timers;
using Gtk;
using Mono.TextEditor;
using Mono.TextEditor.Highlighting;
using PingPong.Generic.ExtensionMethods;
using PingPong.GameEngine;
using PingPong.Generic.Helpers;
using PingPong.Editor;
using Mono.TextEditor.PopupWindow;
using PingPong.GameEngine.Components;

public partial class MainWindow: Gtk.Window
{
	#region Members

	/// <summary>
	/// Describes if the document is dirty.
	/// </summary>
	private bool documentIsDirty;

	/// <summary>
	/// Describes if the document exists on disk.
	/// </summary>
	private bool documentOnDisk;

	/// <summary>
	/// The document path.
	/// </summary>
	private string documentPath;

	/// <summary>
	/// The auto build timer.
	/// </summary>
	private Timer autoBuildTimer;

	/// <summary>
	/// The text line marker cache.
	/// </summary>
	private List<TextLineMarker> textLineMarkerCache;

	/// <summary>
	/// The code helper list.
	/// </summary>
	private List<Tuple<string, string>> codeHelperList;

	#endregion

	/// <summary>
	/// Initializes a new instance of the <see cref="MainWindow"/> class.
	/// </summary>
	public MainWindow() : base(Gtk.WindowType.Toplevel)
	{
		Build();
		this.autoBuildTimer = new Timer(1000);
		this.textLineMarkerCache = new List<TextLineMarker>();
		SyntaxModeService.LoadStylesAndModes("../../Syntax/");
		this.txtEditor.Document.MimeType = "text/x-gameDefinition";
		this.txtEditor.Caret.PositionChanged += (sender, e) => 
		{
			this.autoBuildTimer.Stop();

			LoadPossibleCodeHelpComponents();

			this.autoBuildTimer.Start();
		};
		this.autoBuildTimer.Elapsed += (sender, e) => 
		{
			this.autoBuildTimer.Stop();
			ParseScript();
		};

		PopulateCodeHelperList();
		Clear();
		LoadDefaultScript();
	}


	#region Methods

	/// <summary>
	/// Saves the file.
	/// </summary>
	protected void SaveFile()
	{
		if (this.documentOnDisk && this.documentIsDirty)
		{
			WriteCurrentFileToDisk();
		}
		else if (this.documentIsDirty)
		{
			SaveFileAs();
		}
	}

	/// <summary>
	/// Saves the file as a specified file name.
	/// </summary>
	protected void SaveFileAs()
	{
		using (var fcd = new FileChooserDialog("Save...", this, FileChooserAction.Save, "Save", ResponseType.Accept, "Cancel", ResponseType.Cancel))
		{
			if (fcd.Run() == (int)ResponseType.Accept)
			{
				this.documentPath = fcd.Filename;
				WriteCurrentFileToDisk();
				fcd.Destroy();
			}
		}
	}

	/// <summary>
	/// Clear the document.
	/// </summary>
	protected void Clear()
	{
		this.documentIsDirty = false;
		this.documentPath = string.Empty;
		this.documentOnDisk = false;
		this.txtEditor.Text = string.Empty;
	}

	/// <summary>
	/// Populates the code helper list.
	/// </summary>
	private void PopulateCodeHelperList()
	{
		this.codeHelperList = new List<Tuple<string, string>>();
		this.codeHelperList.AddRange(new[] {
			"define",
			"as",
			"RotationType",
			"TeamCount",
			"TeamSize",
			"ScoringType",
			"when",
			"or",
			"and",
			"then",
			"call"
		}.Select(item => new Tuple<string, string>(item, "core")));

		PopulateCodeHelperEnumType(typeof(ScoringType),"Scoring Type");
		PopulateCodeHelperEnumType(typeof(TeamRotationType),"Team Rotation Type");
		PopulateCodeHelperEnumType(typeof(GameActionType),"Game Action Type");


		this.nodeCodeHelper.NodeStore = new NodeStore(typeof(CodeHelperNode));
		this.nodeCodeHelper.AppendColumn("Code Component",new Gtk.CellRendererText(),"text",0);
		this.nodeCodeHelper.AppendColumn("Component Type",new Gtk.CellRendererText(),"text",1);

	}

	private void PopulateCodeHelperEnumType(Type enumType, string typeFriendlyName)
	{
		this.codeHelperList.AddRange(Enum.GetNames(enumType).Select(item => new Tuple<string, string>(item, typeFriendlyName)));
	}

	void LoadPossibleCodeHelpComponents()
	{
		var offset = this.txtEditor.Caret.Offset;
		int wordStart = FindWordStart(offset);
		var word = this.txtEditor.GetTextBetween(wordStart, offset);
		this.nodeCodeHelper.NodeStore.Clear();
		var nodes = this.codeHelperList.Where(item => item.Item1.StartsWith(word, StringComparison.OrdinalIgnoreCase)).Select(item => new CodeHelperNode(item.Item1, item.Item2));
		foreach (var node in nodes)
		{
			this.nodeCodeHelper.NodeStore.AddNode(node);
		}
		var topNode = this.nodeCodeHelper.NodeStore.Cast<CodeHelperNode>().FirstOrDefault();
		if (topNode != null)
		{
			this.nodeCodeHelper.NodeSelection.SelectNode(topNode);
		}
	}

	/// <summary>
	/// Finds the word start.
	/// </summary>
	/// <returns>The word start.</returns>
	/// <param name="offset">Offset.</param>
	private int FindWordStart(int offset)
	{
		if(offset <= 0)
		{
			return 0;
		}

		for (int i = offset; true; i--)
		{
			if (i <= 0 || string.IsNullOrWhiteSpace(this.txtEditor.GetCharAt(i - 1).ToString()))
			{
				return i;
			}
		}
	}

	/// <summary>
	/// Autos the complete.
	/// </summary>
	void AutoComplete()
	{
		if (this.nodeCodeHelper.NodeSelection.SelectedNode != null)
		{
			var codeComponent = this.nodeCodeHelper.NodeSelection.SelectedNode as CodeHelperNode;
			if (codeComponent != null)
			{
				var temp = new {
					Column = this.txtEditor.Caret.Column,
					Line = this.txtEditor.Caret.Line
				};
				this.txtEditor.TextArea.SetSelection(FindWordStart(this.txtEditor.Caret.Offset), this.txtEditor.Caret.Offset);
				this.txtEditor.TextArea.SelectedText = codeComponent.CodeComponent;
				this.txtEditor.SetSelection(temp.Line, temp.Column, temp.Line, temp.Column);
			}
		}
	}

	/// <summary>
	/// Reads a file from disk.
	/// </summary>
	private void ReadFileFromDisk()
	{
		using (var fcd = new FileChooserDialog("Open...", this, FileChooserAction.Open, "Open", ResponseType.Accept, "Cancel", ResponseType.Cancel))
		{
			if (fcd.Run() == (int)ResponseType.Accept)
			{
				this.documentPath = fcd.Filename;
				TextReader reader = new StreamReader(this.documentPath);
				this.txtEditor.Text = reader.ReadToEnd();
				fcd.Destroy();
			}
		}
	}

	/// <summary>
	/// Writes the current file to disk.
	/// </summary>
	private void WriteCurrentFileToDisk()
	{
		using (TextWriter file = new StreamWriter(this.documentPath))
		{
			file.Write(this.txtEditor.Text);
			file.Close();
		}
	}

	/// <summary>
	/// Alerts the and query save.
	/// </summary>
	void AlertAndQuerySave()
	{
		if (this.documentIsDirty)
		{
			using (var alert = new MessageDialog(this, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, "You have unsaved changes. Do you want to continue?"))
			{
				if (alert.Run() == (int)ResponseType.Yes)
				{
					SaveFile();
				}
				alert.Destroy();
			}
		}
	}
		
	/// <summary>
	/// Loads the default script.
	/// </summary>
	private void LoadDefaultScript()
	{
		var defaultScript = ResourceHelper.GetTextResource("PingPong.Editor.Scripts.Default.txt");

		this.txtEditor.Text = defaultScript;
	}

	/// <summary>
	/// Parses the script.
	/// </summary>
	private void ParseScript()
	{
		foreach(var textLineMarker in this.textLineMarkerCache)
		{
			this.txtEditor.Document.RemoveMarker(textLineMarker);
		}
		var gameDefinition = Engine.ParseGameDefinitionScript(this.txtEditor.Text);
		if (gameDefinition.Errors.Any())
		{
			this.lblStatusLabel.Text = gameDefinition.Errors.Count() + " Errors";

			foreach(var error in gameDefinition.Errors)
			{
				var marker = new UnderlineMarker(new Cairo.Color(1,0,0), error.Column, error.Column + error.Token.val.Length);
				this.textLineMarkerCache.Add(marker);
				this.txtEditor.Document.AddMarker(error.Line,marker);
			}
			this.txtEditor.QueueDraw();
			this.textview2.Buffer.Text = string.Join(Environment.NewLine,gameDefinition.Errors.Select(err =>string.Format("{0} - Token: {1} - Line: {2} - Column: {3}", err.Description, err.Token.val, err.Line, err.Column)));
		}
		else
		{
			this.lblStatusLabel.Text = "Compiled successfully.";
			this.textview2.Buffer.Text = string.Empty;
		}
	}

	#endregion

	#region Event Handlers

	protected void OnDeleteEvent(object sender, DeleteEventArgs a)
	{
		Application.Quit();
		a.RetVal = true;
	}

	protected void UndoClicked(object sender, EventArgs e)
	{
		this.txtEditor.Document.Undo();
	}

	protected void PasteClicked(object sender, EventArgs e)
	{
		this.txtEditor.InsertAtCaret(this.GetClipboardText());
	}

	protected void CutClicked(object sender, EventArgs e)
	{
		var val = this.txtEditor.SelectedText;
		this.SetClipboardTextIgnoreBlankOrNull(val);
		this.txtEditor.SelectedText = string.Empty;
	}

	protected void CopyClicked(object sender, EventArgs e)
	{
		this.SetClipboardTextIgnoreBlankOrNull(this.txtEditor.SelectedText);
	}

	protected void RedoClicked(object sender, EventArgs e)
	{
		this.txtEditor.Document.Redo();
	}

	protected void NewScriptClicked(object sender, EventArgs e)
	{
		AlertAndQuerySave();

		Clear();

		LoadDefaultScript();
	}

	protected void OpenScriptClicked(object sender, EventArgs e)
	{
		AlertAndQuerySave();

		ReadFileFromDisk();
	}

	protected void SaveScriptClicked(object sender, EventArgs e)
	{
		SaveFile();
	}

	protected void SaveAsClicked(object sender, EventArgs e)
	{
		SaveFileAs();
	}

	protected void BuildScriptChecked (object sender, EventArgs e)
	{
		ParseScript();
	}

	protected void AutoCompleteActivated (object sender, EventArgs e)
	{
		AutoComplete();
	}
	#endregion

}

